import 'reflect-metadata'
import { Logger } from './logger'
import express, { Express, Request as ExpressRequest, Response as ExpressResponse, NextFunction } from 'express'
import path from 'path'
// import { LoggerService, UseValueService } from '../../logger.service'
import { RequestMethod } from '@nestjs/common'
import { INJECTED_TOKENS, DESIGN_PARAMTYPES } from '../common/constants'
import { defineModule } from '../common/module.decorator'
export class NestApplication {
  // 在它的内部私有化一个Express实例
  private readonly app: Express = express()
  // 在此处保存全部的providers key是provider的token，值就是provider实例或者说值
  private readonly providerInstance = new Map()
  // 此处存放着全局可用的提供者和token
  private readonly globalProviders = new Set()
  // 记录每个模块里有哪些provider的token
  private readonly moduleProviders = new Map()
  // 记录所有的中间件 可能是中间件的类，可能是中间件的实例，也可能是一个函数中间件
  private readonly middlewares = []
  // 记录所有的要排除的路径
  private readonly excludedRoutes = []
  constructor(protected readonly module) {
    this.app.use(express.json()) // 用来把JSON格式的请求体对象放在req.body上
    this.app.use(express.urlencoded({ extended: true })) // 把form表单格式的请求体对象放在req.body上
    // this.app.use((req, res, next) => {
    //   req.user = { name: 'admin', role: 'admin' }
    //   next()
    // })
  }
  private initMiddlewares() {
    // 调用配置中间件的方法，MiddlewareConsumer就是当前的NestApplication的实例
    this.module.prototype.configure?.(this)
  }
  apply(...middleware) {
    // 把接收到的中间放到中间数组中，并且返回当前的实例
    defineModule(this.module, middleware)
    this.middlewares.push(...middleware)
    return this
  }
  getMiddlewareInstance(middleware) {
    if (middleware instanceof Function) {
      const dependencies = this.resolveDependencies(middleware)
      // console.log('dependencies: ', dependencies)
      return new middleware(...dependencies)
    }
    return middleware
  }
  forRoutes(...routes) {
    // 遍历路径信息
    for(const route of routes) {
      // 遍历中间件
      for(const middleware of this.middlewares) {
        // 把route格式化为标准对象，一个是路径，一个是请求方法
        const { routePath, routeMethod } = this.normalizeRouteInfo(route)  
        // use方法的第一个参数表示匹配的路径，如果不匹配根本就进不来
        this.app.use(routePath, (req, res, next) => {
          // 如果当前的路径要排除掉的话，那么不走当前的中间件了
          if (this.isExcluded(req.originalUrl, req.method)) {
            return next()
          }
          // 如果配置方法名是All，或者方法相同完全匹配
          if (routeMethod === RequestMethod.ALL || routeMethod === req.method) {
            // 此处middleware可能是一个类，也可能是一个实例，也可能只是一个函数
            if ('use' in middleware.prototype || 'use' in middleware) {
              const middlewareInstance = this.getMiddlewareInstance(middleware)
              middlewareInstance.use(req, res, next)
            } else if(middleware instanceof Function ) {
              middleware(req, res, next)
            } else {
              next()
            }
          } else {
            next()
          }
        })
      }
    }
    return this
  }
  isExcluded(reqPath: string, method: RequestMethod) {
    // 遍历要排除的路径，看看哪一个排除的路径和当前的请求路径和方法名匹配
    // console.log(this.excludedRoutes);
    // console.log(reqPath, method, 'center::::');
    
    return this.excludedRoutes.some(routeInfo => {
      const { routePath, routeMethod } = routeInfo
      // console.log(routePath, routeMethod);
      
      return routePath === reqPath && (routeMethod === RequestMethod.ALL || routeMethod === method)
    })
  }
  exclude(...routeInfos): this {
    this.excludedRoutes.push(...routeInfos.map(this.normalizeRouteInfo))
    return this
  }
  private normalizeRouteInfo(route) {
    let routePath = '' // 转化路径
    let routeMethod = RequestMethod.ALL // 默认是支持所有的方法
    if (typeof route === 'string') {
      routePath = route // 传的就是一个路径
    } else if ('path' in route) {
      // 如果传入的是一个路径对象
      routePath = route.path
      routeMethod = route.method??RequestMethod.ALL 
    } else if (route instanceof Function) {
      // 如果route是一个控制器的话，以它的路径前缀作为路径
      routePath = Reflect.getMetadata('prefix', route)
    }
    // cats => /cats
    routePath = path.posix.join('/', routePath)
    return {
      routePath,
      routeMethod
    }
  }
  // 初始化提供化
  async initProviders() {
    // const providers = Reflect.getMetadata('providers', this.module)??[]
    // for(const provider of providers) {
    //   // 如果provider是一个类
    //   if (provider.provide && provider.useClass) {
    //     const dependencies = this.resolveDependencies(provider.useClass)
    //     // 创建类的实例
    //     const classInstance = new provider.useClass(...dependencies) // 此处尚未完成，因为这个类可能还会有依赖
    //     // 把provider的token和类的实例保存到this.providers里
    //     this.providers.set(provider.provide, classInstance)
    //   } else if (provider.provide && provider.useValue) {
    //     // 提供的是一个值，则不需要容器帮忙实例化，直接使用此值注册就可以了
    //     this.providers.set(provider.provide, provider.useValue)
    //   } else if (provider.provide && provider.useFactory) {
    //     //
    //     const inject = provider.inject??[]
    //     // const injectedValue = inject.map((injectedToken) => this.getProviderByToken(injectedToken))
    //     const injectedValue = inject.map(this.getProviderByToken.bind(this))
    //     this.providers.set(provider.provide, provider.useFactory(...injectedValue))
    //   } else {
    //     const dependencies = this.resolveDependencies(provider)
    //     // 表示只提供了一个类，token是这个类，值是这个类的实例
    //     this.providers.set(provider, new provider(...dependencies))
    //   }
    // }
    // console.log('this.providers: ', this.providers);

    // 重写provider的流程
    // 获取模块导入的元数据
    const imports = Reflect.getMetadata('imports', this.module)??[]
    // 遍历所有的导入的模块
    for(const importModule of imports) {
      let importedModule = importModule
      // 如果导入的是一个Promise，则说明它是异步的动态模块
      if (importedModule instanceof Promise) {
        importedModule = await importedModule
      }
      // 如果导入的模块有module属性，说明这是一个动态模块
      if ('module' in importedModule) {
        const { module, providers, exports, controllers } = importedModule
        const oldProviders = Reflect.getMetadata('providers', module)??[]
        const newProviders = [...oldProviders, ...(providers??[])]
        const oldExports = Reflect.getMetadata('exports', module)??[]
        const newExports = [...oldExports, ...(exports??[])]
        const oldControllers = Reflect.getMetadata('controllers', module)??[]
        const newControllers = [...oldControllers, ...(controllers??[])]
        defineModule(module, newControllers)
        defineModule(module, newProviders)
        Reflect.defineMetadata('providers', newProviders, module)
        Reflect.defineMetadata('exports', newExports, module)
        Reflect.defineMetadata('controllers', newControllers, module)
        this.registerProvidersFromModule(module, this.module)
      } else {
        this.registerProvidersFromModule(importedModule, this.module)
      }
    }
    // 获取当前模块提供者的元数据
    const providers = Reflect.getMetadata('providers', this.module)??[]
    // 遍历并添加每个提供者
    for(const provider of providers) {
      this.addProvider(provider, this.module)
    }
  }
  private registerProvidersFromModule(module, ...parentModules) {
    // 获取导入的是不是全局模块
    const global = Reflect.getMetadata('global', module)

    // 拿到导入的模块providers进行全量注册

    // 获取导入模块中的提供者元数据[SUFFIX, LoggerClassService]
    const importedProviders = Reflect.getMetadata('providers', module)??[]
    // 1.有可能导入的模块只导出了一部分，并没有全量导出，所以需要使用exports进行过滤
    const exports = Reflect.getMetadata('exports', module) ?? []
    // 遍历导出exports数组
    for(const exportToken of exports) {
      // 2.exports里还可能有module
      if(this.isModule(exportToken)) {
        // 要执行递归操作
        this.registerProvidersFromModule(exportToken, module, ...parentModules)
      } else {
        const provider = importedProviders.find(provider => provider === exportToken || provider.provide === exportToken)
        if (provider) {
          [module, ...parentModules].forEach(module => {
            this.addProvider(provider, module, global)
          })
        }
      }
    }
  }
  private isModule(exportToken) {
    return exportToken && exportToken instanceof Function && Reflect.getMetadata('isModule', exportToken)
  }
  // 原来的provider都混在一起了，现在需要分开，每个模块都有自己的providers
  addProvider(provider, module, global = false) {
    // 此providers代码module这个模块对应的provider的token
    const providers = global ? this.globalProviders : (this.moduleProviders.get(module) || new Set())
    if (!this.moduleProviders.has(module)) {
      this.moduleProviders.set(module, providers)
    }
    // 获取要注册的provider的token
    let injectToken = provider.provide??provider
    // 如果实例池里有此token对应的实例了
    if (this.providerInstance.has(injectToken)) {
      // 则直接把此token放入providers这个集合直接返回
      providers.add(injectToken)
      return
    }
    // 为了避免循环依赖，每次添加前可以做一个判断，如果Map中已经存在，则直接返回
    // const injectToken = provider.provide??provider
    // if (this.providers.has(injectToken)) return
    // 如果有provider的token，并且有useClass属性，说明提供的是一个类，需要实例化
    if (provider.provide && provider.useClass) {
      // 获取这个类的定义
      const Clazz = provider.useClass
      // 获取此类的参数['suffix']
      const dependencies = this.resolveDependencies(Clazz)
      // 创建提供者类的实例
      const value = new Clazz(...dependencies)
      // 把提供者的token和实例保存到Map中
      this.providerInstance.set(provider.provide, value)
    } else if (provider.provide && provider.useValue) {
      // 如果提供的是一个值，则直接放到Map中
      this.providerInstance.set(provider.provide, provider.useValue)
      providers.add(provider.provide)
    } else if (provider.provide && provider.useFactory) {
      const inject = provider.inject??[] // 获取要注入工厂函数的参数
      // 解析出参数的值
      const injectedToken = inject.map(injectedToken => this.getProviderByToken(injectedToken, module))
      // 执行工厂方法，获取返回的值
      const value = provider.useFactory(...injectedToken)
      // 把token和值注册到Map中
      this.providerInstance.set(provider.provide, value)
      providers.add(provider.provide)
    } else {
      const dependencies = this.resolveDependencies(provider)
      const value = new provider(...dependencies)
      this.providerInstance.set(provider, value)
      providers.add(provider)
    }
  }
  use(middleware) {
    this.app.use(middleware)
  }
  private getProviderByToken = (injectedToken, module) => {
    // 如何通过token在特定模块下找对应的provider
    // 先找到此模块对应的token set，再判断此injectedToken在不在此set中，如果存在，则返回对应的provider实例
    if (this.moduleProviders.get(module)?.has(injectedToken) || this.globalProviders.has(injectedToken)) {
      return this.providerInstance.get(injectedToken)
    } else {
      return null
    }
    // return this.providers.get(injectedToken)??injectedToken
  }
  private resolveDependencies(Controller) {
    const injectedTokens = Reflect.getMetadata(INJECTED_TOKENS, Controller)??[]
    // console.log('injectedTokens: ', injectedTokens)
    const constructorParams = Reflect.getMetadata(DESIGN_PARAMTYPES, Controller)??[]
    // console.log('constructorParams: ', constructorParams)
    return constructorParams.map((param, index) => {
      // console.log(index, 'injectedToken', injectedTokens[index], 'param', param)
      const module = Reflect.getMetadata('nestModule', Controller)
      // 把每个param中token默认换成对应的provider值
      return this.getProviderByToken(injectedTokens[index]??param, module)
    })
  }
  // 配置初始化工作
  async init() {
    // 取出模块里所有的控制器，然后做好路由配置
    const controllers = Reflect.getMetadata('controllers', this.module) || []
    Logger.log(`AppModule dependencies initialized`, 'InstanceLoader')
    // 路由映射的核心是知道 什么样的请求方法什么样的路径对应的是哪个处理函数
    for(const Controller of controllers) {
      const dependencies = this.resolveDependencies(Controller)
      // 创建每个控制器的实例
      const controller = new Controller(...dependencies)
      // 获取控制器的路径前缀
      const prefix = Reflect.getMetadata('prefix', Controller) || '/'
      // 开始解析路由
      Logger.log(`${Controller.name} {${prefix}}`, 'RoutesResolver')
      const controllerPrototype = Controller.prototype
      // 遍历类的原型上的方法名
      for(const methodName of Object.getOwnPropertyNames(controllerPrototype)) {
        // 获取原型上的方法 index
        const method = controllerPrototype[methodName]
        // 获取此函数上绑定的方法名的元数据
        const httpMethod = Reflect.getMetadata('method', method)
        // 获取此函数上绑定的路径的元数据
        const pathMetadata = Reflect.getMetadata('path', method)
        const redirectUrl = Reflect.getMetadata('redirectUrl', method)
        const redirectStatusCode = Reflect.getMetadata('redirectStatusCode', method)
        const statusCode = Reflect.getMetadata('statusCode', method)
        const headers = Reflect.getMetadata('headers', method)??[]
        // 如果方法名不存在，则不处理
        if (!httpMethod) continue
        // 拼出来完整的路由路径
        const routePath = path.posix.join('/', prefix, pathMetadata)
        // 配置路由，当客户端以httpMethod方法请求routePath路径的时候，会由对应的函数进行处理
        this.app[httpMethod.toLowerCase()](routePath, (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
          const args = this.resolveParams(controller, methodName, req, res, next)
          // console.log(args, 'args::::');
          // 执行路由处理函数，获取返回值
          const result = method.call(controller, ...args)
          // console.log(result, 'result:::::')
          if (result?.url) {
            return res.redirect(result.statusCode || 302, result.url)
          }
          // 判断如果需要重定向，则直接重定向到指定的redirectUrl
          if (redirectUrl) {
            res.redirect(redirectStatusCode || 302, redirectUrl)
          }
          // console.log('statusCode: ', statusCode)
          if (statusCode) {
            res.statusCode = statusCode
          } else if (httpMethod === 'POST') {
            res.statusCode = 201
          }
          // 判断Controller的methodName方法里面有没有使用Response或Res参数装饰器，如果用了任何一个则不发相应交由他们自己处理
          const responseMetadata = this.getResponseMetadata(controller, methodName)
          // 没有注入Response或Res参数装饰器或者注入了但是传递了passthrough参数为true，都会由Nestjs来返回响应
          if(!responseMetadata || responseMetadata?.data?.passthrough) {
            headers.forEach(({ name, value }) => {
              res.setHeader(name, value)
            })
            // 把返回值序列化发回给客户端
            res.send(result)
          }
        })
        Logger.log(`Mapped {${routePath}, ${httpMethod}} route`, 'RoutesResolver')
      }
    }
    Logger.log(`Nest application successfully started`, 'NestApplication')
  }
  private getResponseMetadata(controller, methodName) {
    const paramsMetadata = Reflect.getMetadata('params', controller, methodName)??[]
    return paramsMetadata.filter(Boolean).find(param => param.key === 'Response' || param.key === 'Res' || param.key === 'Next')
  }
  private resolveParams(instance: any, methodName: string, req: ExpressRequest, res: ExpressResponse, next: NextFunction) {
    // 获取参数的元数据
    const paramsMetadata = Reflect.getMetadata('params', instance, methodName)??[]
    // console.log(paramsMetadata, 'paramsMetadata::::;');
    
    // [{ 'parameterIndex': 0, key: 'Req' }, { 'parameterIndex': 1, key: 'Request' }]
    // return paramsMetadata.sort((a, b) => a.parameterIndex - b.parameterIndex).map(paramMetadata => {
    return paramsMetadata.map(paramMetadata => {
      const { key, data, factory } = paramMetadata
      const ctx = {// 因为Next不但支持http，还支持graphql 微服务 websocket
        switchToHttp: () => ({
          getRequest: () => req,
          getResponse: () => res,
          getNext: () => next
        })
      }    
      switch(key) {
        case 'Request':
        case 'Req':
          return req
        case 'Query':
          return data ? req.query[data] : req.query
        case 'Headers':
          return data ? req.headers[data] : req.headers
        case 'Session':
          return data ? req.session[data] : req.session
        case 'Ip':
          return req.ip
        case 'Param':
          return data ? req.params[data] : req.params
        case 'Body':
          return data ? req.body[data] : req.body
        case 'Response':
        case 'Res':
          return res
        case 'Next':
          return next
        case 'DecoratorFactory':
        return factory(data, ctx)
        default:
          return null
      }
    })  
    // [req, req]
  }
  // 启动HTTP服务器
  async listen(port) {
    await this.initProviders() // 注入providers
    this.initMiddlewares() // 初始化中间件配置
    await this.init()
    // 调用express实例的listen方法启动一个HTTP服务器，监听port端口
    this.app.listen(port, () => {
      Logger.log(`Applicatin is running on http://localhost:${port}`, 'NestApplication')
    })
  }
}